﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.IO.Ports;

namespace PowerMonitor.Common
{
    public class ModbusInfo
    {
        public int DevId;
        public byte DevAddr;
        public byte FunCode;
        public ushort RegAddr;
        public ushort RegNum;
        public int startIndex;
        public byte[] WriteData;
        public byte[] SendADU;
        public byte[] RecADU;
        public  ModbusInfo()
        {
            DevId = 0;
            DevAddr =1;
            FunCode = 0x03;
            RegAddr = 0x0000;
            RegNum = 0x0001;
            startIndex = 0;
            WriteData = null;
            SendADU = null;
            RecADU = null;
        }
    };
    public class Modbus
    {

        public const ushort  MB_NO_ERROR    = 0x0000;
        public const ushort MB_ADDR_ERROR   = 0x0001;
        public const ushort MB_FUN_ERROR    = 0x0002;
        public const ushort MB_CRC16_ERROR  = 0x0004;
        public const ushort MB_FRAME_ERROR  = 0x0008;
        public const ushort MB_LEN_ERROR    = 0x0010;
        public const ushort COMPORT_NOT_OPEN = 0xFFFF;

        static object ProSned = new object();
        static object ProRec = new object();

        static object stcProRec = new object();
        public Modbus()
        {

        }


        public static ushort GetCRC16(byte[] Datain)
        {
            ushort wCRC = 0xFFFF;
            for (int i = 0; i < Datain.Length - 2; i++)
            {
                wCRC = (ushort)(wCRC ^ Datain[i]);
                for (int j = 0; j < 8; j++)
                {
                    if ((wCRC & 0x01) == 1)
                    {
                        wCRC = (ushort)((wCRC >> 1) ^ 0xA001);
                    }
                    else
                    {
                        wCRC = (ushort)((wCRC) >> 1);
                    }
                }
            }
            return wCRC;
        }


        static bool  CheckCRC(byte[] pbyInBuf) // Check CRC
        {
            ushort crc16;
            if (pbyInBuf.Length > 2)
            {
                crc16 = BitConverter.ToUInt16(pbyInBuf, pbyInBuf.Length - 2);
            }
            else
            {
                return false;
            }

            if (crc16 == GetCRC16(pbyInBuf)) 
            {
                return true;
            }
            else
            {
                return false;
            }
        }
        /// <summary>
        /// 打包发送数据  需要先初始化ModbusInfo结构中除byte[] 数组外的信息，
        /// DataTag为附带信息 如功能码 0x05 0x06 0x10 需对其赋值 
        /// </summary>
        /// <param name="SndMb"></param>
        /// <returns></returns>
        static public bool MakeSendFrame(ref ModbusInfo SndMb)
        {
            byte[] byToSend = null;
            int length = 0;

            if (!Monitor.TryEnter(ProSned, 100))
            {
                return false;  
            }
            if (SndMb == null)
            {
                throw new NullReferenceException();
                //return false;
            }
            try
            {
                switch (SndMb.FunCode)
                {
                    case 0x03:
                        byToSend = new byte[8];
                        byToSend[2] = (byte)(SndMb.RegAddr >> 8);
                        byToSend[3] = (byte)(SndMb.RegAddr & 0x00FF);
                        byToSend[4] = (byte)(SndMb.RegNum >> 8);
                        byToSend[5] = (byte)(SndMb.RegNum & 0x00FF);
                        length = 6;
                        break;
                    case 0x05:
                    case 0x06:
                        byToSend = new byte[8];
                        if (SndMb.WriteData.Length < 2)
                        {
                            return false;
                        }
                        byToSend[2] = (byte)(SndMb.RegAddr >> 8);
                        byToSend[3] = (byte)(SndMb.RegAddr & 0x00FF);
                        byToSend[4] = SndMb.WriteData[1] ; // 高字节
                        byToSend[5] = SndMb.WriteData[0] ; // 低字节       
                        length = 6;
                        break;
                    case 0x10:
                        if (SndMb.WriteData == null || SndMb.WriteData.Length != SndMb.RegNum * 2)
                        {
                            return false;
                        }
                        byToSend = new byte[SndMb.WriteData.Length + 9];

                        byToSend[2] = (byte)(SndMb.RegAddr >> 8);
                        byToSend[3] = (byte)(SndMb.RegAddr & 0x00FF);
                        byToSend[4] = (byte)(SndMb.RegNum >> 8);
                        byToSend[5] = (byte)(SndMb.RegNum & 0x00FF);
                        byToSend[6] = (byte)(SndMb.RegNum * 2);
                        for (int i = 0; i < SndMb.WriteData.Length; i++)
                        {
                            byToSend[7 + i] = SndMb.WriteData[i];
                        }
                        length = (ushort)(SndMb.WriteData.Length + 7);                      
                        break;
                    default:
                        byToSend = new byte[2];
                        length = 0;
                        break;

                }
                byToSend[0] = SndMb.DevAddr;
                byToSend[1] = SndMb.FunCode;
                if (length == 0)
                {
                    return false;
                }
                else
                {
                    ushort crc = GetCRC16(byToSend);
                    byToSend[length] = (byte)(crc & 0x00FF);
                    byToSend[length + 1] = (byte)(crc >> 8);
                    SndMb.SendADU = byToSend;
                    //SndMb.RecADU = null;
                }
                return true;
            }
            finally
            {
                Monitor.Exit(ProSned);
            }
        }
        /// <summary>
        /// 检查接收数据正确性
        /// </summary>
        /// <param name="ModData"></param>
        /// <returns></returns>
        static public ushort CheckRecFrame(ref ModbusInfo ModData)  // check receive data frame
        {
            if (Monitor.TryEnter(ProRec,200) == false) // 等待失败
            {
                return MB_FRAME_ERROR;
            }
            
            if (ModData == null || ModData.SendADU == null || ModData.RecADU == null)
            {
                Monitor.Exit(ProRec);
                //throw new NullReferenceException();
                return MB_FRAME_ERROR;
            }
            byte[] pBuffer = ModData.RecADU;
            ushort length = (ushort)ModData.RecADU.Length;
            try
            {
                if (pBuffer.Length < 5 || length < 5)
                {
                    return MB_LEN_ERROR;
                }
                else if (ModData.DevAddr != pBuffer[0])
                {
                    return MB_ADDR_ERROR;
                }
                else if (ModData.FunCode != pBuffer[1] && ModData.FunCode != pBuffer[1] - 0x80)
                {
                    return MB_FUN_ERROR;
                }
                else if (!CheckCRC(pBuffer))
                {
                    return MB_CRC16_ERROR;
                }
                else
                {
                    if (ModData.FunCode < 0x80)
                    {
                        switch (ModData.FunCode)
                        {
                            case 0x10:
                                if (length != 8)
                                {
                                    return MB_LEN_ERROR;
                                }                               
                                break;
                            case 0x0f:
                            case 0x02:
                            case 0x01:
                                break;
                            case 0x03: 
                            case 0x04:
                                if (length != pBuffer[2] + 5)
                                {
                                    return MB_LEN_ERROR;
                                }                               
                                break;
                            case 0x05:
                            case 0x06:
                                if (length != 8 )
                                {
                                    return MB_LEN_ERROR;
                                }      
                                else if (pBuffer[4] == ModData.SendADU[4] || pBuffer[5] == ModData.SendADU[5])
                                {
                                    return MB_FRAME_ERROR;
                                }
                                break;
                            default:
                                {
                                    return MB_FUN_ERROR;
                                }
                        }
                        return MB_NO_ERROR;
                    }
                    else
                    {
                        return MB_FRAME_ERROR;
                    }
                }
            }
            finally
            {
                Monitor.Exit(ProRec);
            }
        }      

    }
}
